{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "e6b971c0",
   "metadata": {},
   "source": [
    "# DeepFM 广告点击率预测"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7496cf63-bdb5-4eaf-9bcf-856b53bd18ed",
   "metadata": {},
   "source": [
    "\n",
    "推荐系统和广告CTR预估主流模型的演化有两条主要路线。\n",
    "\n",
    "第一条是显式建模特征交互，提升模型对交叉特征的捕获能力。(如Wide&Deep,PNN,FNN,DCN,DeepFM,AutoInt等)\n",
    "\n",
    "第二条是加入注意力机制，提升模型的自适应能力和解释性。(如DIN,DIEN,DSIN,FiBiNET,AutoInt等)\n",
    "\n",
    "在所有这些模型中，DeepFM属于性价比非常高的模型（结构简洁，计算高效，指标有竞争力）。\n",
    "\n",
    "张俊林大佬 在2019年的时候甚至建议 沿着 LR->FM->DeepFM->干点别的 这样的路线去迭代推荐系统。\n",
    "\n",
    "\n",
    "\n",
    "本范例演示使用 torchkeras.tabular.DeepFMModel 进行广告点击率预测。\n",
    "\n",
    "有关DeepFM模型的完整理论介绍，参考如下eat_pytorch_in_20_days中的讲解教程：\n",
    "\n",
    "https://github.com/lyhue1991/eat_pytorch_in_20_days/blob/master/7-4%2CDeepFM%E6%A8%A1%E5%9E%8B.ipynb\n",
    "\n",
    "\n",
    "\n",
    "公众号**算法美食屋**后台回复关键词：torchkeras，获取本文notebook源码和所用criteo_small数据集下载链接。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "81b4a7a1-a2d8-4d4c-a0ad-224ab0e9a9fc",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "fa19852f",
   "metadata": {},
   "source": [
    "## 一，准备数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "85cbdc51",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "from sklearn.preprocessing import OrdinalEncoder\n",
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "np.random.seed(42)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "0f799967",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from sklearn.preprocessing import LabelEncoder\n",
    "\n",
    "dfdata = pd.read_csv('criteo_small.zip',sep='\\t',header=None)\n",
    "dfdata.columns = [\"label\"] + [\"I\"+str(x) for x in range(1,14)] + [\n",
    "    \"C\"+str(x) for x in range(14,40)]\n",
    "\n",
    "target_col = 'label'\n",
    "cat_cols = [x for x in dfdata.columns if x.startswith('C')]\n",
    "num_cols = [x for x in dfdata.columns if x.startswith('I')]\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "6a809254",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "dftrain_val,dftest_raw = train_test_split(dfdata,test_size=0.2,random_state=42)\n",
    "dftrain_raw,dfval_raw = train_test_split(dftrain_val,test_size=0.2,random_state=42)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "2fde517f",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2024-09-04 12:47:03.893852: I tensorflow/core/util/port.cc:110] oneDNN custom operations are on. You may see slightly different numerical results due to floating-point round-off errors from different computation orders. To turn them off, set the environment variable `TF_ENABLE_ONEDNN_OPTS=0`.\n",
      "2024-09-04 12:47:03.945771: I tensorflow/core/platform/cpu_feature_guard.cc:182] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.\n",
      "To enable the following instructions: AVX2 AVX512F AVX512_VNNI FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.\n",
      "2024-09-04 12:47:04.728117: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Could not find TensorRT\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "efd1fffeeda7402ba5b7c429f3c9e1f9",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "  0%|          | 0/24 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from torchkeras.tabular import TabularPreprocessor\n",
    "from sklearn.preprocessing import OrdinalEncoder\n",
    "\n",
    "#特征工程\n",
    "pipe = TabularPreprocessor(cat_features = cat_cols, onehot_max_cat_num=3)\n",
    "encoder = OrdinalEncoder()\n",
    "\n",
    "dftrain = pipe.fit_transform(dftrain_raw.drop(target_col,axis=1))\n",
    "dftrain[target_col] = encoder.fit_transform(\n",
    "    dftrain_raw[target_col].values.reshape(-1,1)).astype(np.int32)\n",
    "\n",
    "dfval = pipe.transform(dfval_raw.drop(target_col,axis=1))\n",
    "dfval[target_col] = encoder.transform(\n",
    "    dfval_raw[target_col].values.reshape(-1,1)).astype(np.int32)\n",
    "\n",
    "dftest = pipe.transform(dftest_raw.drop(target_col,axis=1))\n",
    "dftest[target_col] = encoder.transform(\n",
    "    dftest_raw[target_col].values.reshape(-1,1)).astype(np.int32)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "b219ae5c",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from torchkeras.tabular import TabularDataset\n",
    "from torch.utils.data import Dataset,DataLoader \n",
    "\n",
    "def get_dataset(dfdata):\n",
    "    return TabularDataset(\n",
    "                data = dfdata,\n",
    "                task = 'binary',\n",
    "                target = [target_col],\n",
    "                continuous_cols = pipe.get_numeric_features(),\n",
    "                categorical_cols = pipe.get_embedding_features()\n",
    "        )\n",
    "\n",
    "def get_dataloader(ds,batch_size=512,num_workers=0,shuffle=False):\n",
    "    dl = DataLoader(\n",
    "            ds,\n",
    "            batch_size=batch_size,\n",
    "            shuffle=shuffle,\n",
    "            num_workers=num_workers,\n",
    "            pin_memory=False,\n",
    "        )\n",
    "    return dl \n",
    "    \n",
    "ds_train = get_dataset(dftrain)\n",
    "ds_val = get_dataset(dfval)\n",
    "ds_test = get_dataset(dftest)\n",
    "\n",
    "dl_train = get_dataloader(ds_train,batch_size=2048,shuffle=True)\n",
    "dl_val = get_dataloader(ds_val,batch_size=2048,shuffle=False)\n",
    "dl_test = get_dataloader(ds_test,batch_size=2048,shuffle=False)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "5eef69c5",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "for batch in dl_train:\n",
    "    break"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "07cf31ea",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(640000, 45)"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dftrain.shape "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f35e558f",
   "metadata": {},
   "source": [
    "## 二，定义模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "816ad379-8854-4239-9c45-5eb1cabbdbdb",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "input_embed_dim =  32\n",
      "\n",
      " categorical_cardinality =  [1000, 529, 1000, 1000, 246, 15, 1000, 502, 1000, 1000, 1000, 1000, 26, 1000, 1000, 10, 1000, 1000, 1000, 16, 15, 1000, 63, 1000]\n",
      "\n",
      " embedding_dims =  [[1000, 50], [529, 50], [1000, 50], [1000, 50], [246, 50], [15, 8], [1000, 50], [502, 50], [1000, 50], [1000, 50], [1000, 50], [1000, 50], [26, 13], [1000, 50], [1000, 50], [10, 5], [1000, 50], [1000, 50], [1000, 50], [16, 8], [15, 8], [1000, 50], [63, 32], [1000, 50]]\n"
     ]
    }
   ],
   "source": [
    "from torchkeras.tabular.models import DeepFMConfig, DeepFMModel \n",
    "\n",
    "model_config = DeepFMConfig(\n",
    "    task=\"binary\",\n",
    "    deep_layers = \"128-64-32\",\n",
    "    deep_dropout = 0.1\n",
    ")\n",
    "\n",
    "config = model_config.merge_dataset_config(ds_train)\n",
    "\n",
    "print('input_embed_dim = ', config.input_embed_dim)\n",
    "print('\\n categorical_cardinality = ',config.categorical_cardinality)\n",
    "print('\\n embedding_dims = ' , config.embedding_dims)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "784129eb-295c-45f5-8cfe-a2b099a654cf",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "ef0e5773-4f8c-4d84-b64d-cd98e67d1dbb",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "net = DeepFMModel(config = config)\n",
    "\n",
    "#初始化参数\n",
    "net.reset_weights()\n",
    "net.data_aware_initialization(dl_train)\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "db727a39",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor(6.3892, grad_fn=<AddBackward0>)\n"
     ]
    }
   ],
   "source": [
    "output = net.forward(batch)\n",
    "loss = net.compute_loss(output,batch['target'])\n",
    "print(loss)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2d339957",
   "metadata": {},
   "source": [
    "## 三，训练模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "c4b24884",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from torchkeras import KerasModel \n",
    "from torchkeras.tabular import StepRunner \n",
    "KerasModel.StepRunner = StepRunner \n",
    "\n",
    "import torch \n",
    "from torchkeras.metrics import AUC \n",
    "\n",
    "optimizer = torch.optim.AdamW(net.parameters(),lr = 1e-3)\n",
    "#scheduler = torch.optim.lr_scheduler.OneCycleLR(optimizer, max_lr=0.01, \n",
    "#                                                steps_per_epoch=len(dl_train), epochs=20)\n",
    "\n",
    "keras_model = KerasModel(net,\n",
    "                   loss_fn=None,\n",
    "                   optimizer = optimizer,\n",
    "                   metrics_dict = {'auc':AUC()}\n",
    "                   )\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "48077708",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "99d5e7d0",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Detected kernel version 5.4.186, which is below the recommended minimum of 5.5.0; this can cause the process to hang. It is recommended to upgrade the kernel to the minimum version or higher.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[0;31m<<<<<< ⚡️ cuda is used >>>>>>\u001b[0m\n"
     ]
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAikAAAGJCAYAAABPZ6NtAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAB2lUlEQVR4nO3deVhU5dsH8O/MAMMii+yrgmJqiloupGZqamb+LPc0zTWtRFNpUSu1NMW0DM2FLGx509wry6XUtFxwCZdcURRFkR0BRdlmnveP44wM67AMM8D3c13nYuac55xzHxhm7nm2IxNCCBARERGZGLmxAyAiIiIqDpMUIiIiMklMUoiIiMgkMUkhIiIik8QkhYiIiEwSkxQiIiIySUxSiIiIyCQxSSEiIiKTxCSFiIiITBKTFDJ5H330EWQyGVJSUowdSrW5fv06ZDIZvvvuO2OHQga0ePFiNGvWDGq12tihVBtT/H+eOXMmAgMDjR0GFYNJClEJFi5ciF9++cXYYdRq6enpmDhxIlxcXGBjY4Pu3bvj5MmTeu0rk8lKXHr16qUtp/lQLGk5fPiwtuzx48cxadIktG3bFubm5pDJZMWe+7vvviv1mOvWrSsz/szMTHz66aeYMWMG5PKy34qzs7ORl5enx2+mdouKisL06dPRqVMnWFpaQiaT4fr16yWW3759O5588klYWlqiQYMGmDt3LvLz83XKTJs2DWfOnMH27dsNHD2Vl5mxAyAyVQsXLsTgwYPRv39/Y4dSK6nVavTt2xdnzpzBu+++C2dnZ6xatQrdunVDZGQkmjRpUur+//d//1dk3b///otly5bhueee064bOHAg/P39i5R9//33ce/ePbRv3167bufOnfjmm2/QqlUrNGrUCJcvXy723M8880yx5//iiy9w5swZ9OjRo9TYAWDt2rXIz8/H8OHDSyxz4sQJLF++HH/++SeSkpIgk8ng5eWFAQMG4K233ir2umq7iIgILF++HI8//jiaN2+O06dPl1h2165d6N+/P7p164Yvv/wSZ8+exSeffIKkpCSsXr1aW87d3R0vvfQSPvvsM7z44ovVcBWkN0Fk4ubOnSsAiOTk5Go9r42NjRg9enS1nlMjJiZGABDffvutUc5fHTZu3CgAiM2bN2vXJSUlCQcHBzF8+PAKHXP8+PFCJpOJmzdvllouNjZWyGQyMWHCBJ31CQkJ4v79+0IIIYKCgkR53iLv378vbG1tRa9evfQq36pVKzFy5Mhit+Xl5YmgoCAhk8lEly5dxGeffSZ+++03sXXrVrFw4ULRpk0bYWlpKVasWKF3fKaisv/PqampIjMzUwghxJIlSwQAERMTU2zZxx9/XLRu3Vrk5eVp133wwQdCJpOJixcv6pTdsmWLkMlk4urVqxWKiwyDzT1UY6SkpGDo0KGws7ODk5MTpk6diuzs7CLlfvzxR7Rt2xZWVlZwdHTEsGHDcPPmTZ0yV65cwaBBg+Du7g5LS0t4e3tj2LBhyMjIACA1JWRlZeH777/XVuGPGTOm2LgSExNhZmaGjz/+uMi2qKgoyGQyrFixAgCQlpaGd955BwEBAahXrx7s7OzQp08fnDlzppK/HSA3Nxdz5sxB27ZtYW9vDxsbG3Tp0gX79+/XKXfgwAHIZDIcOHBAZ31J/WAuXbqEoUOHwsXFBVZWVmjatCk++OCDSse7ZcsWuLm5YeDAgdp1Li4uGDp0KH799Vfk5OSU63g5OTnYunUrunbtCm9v71LL/vTTTxBCYMSIETrr3dzcYGVlVa7zavz222+4e/dukWMWJyYmBv/99x969uxZ7PaxY8di/fr12LlzJ/755x+8/fbb+N///oeBAwdi1qxZOHXqFMLCwvDOO+8gLCysyP5xcXEYN24c3NzcoFQq0aJFC6xdu1anjOZ1sHHjRrz//vtwd3eHjY0NXnzxxSL/LwCwefNm7f+Vs7MzRo4cibi4uCLl9H29pKenY8yYMXBwcIC9vT3Gjh2L+/fvl/m7c3R0hK2tbZnlLly4gAsXLmDixIkwM3vUaDBp0iQIIbBlyxad8pq/xa+//lrmsan6sLmHaoyhQ4fC19cXISEhOHr0KJYvX447d+7ghx9+0JZZsGABZs+ejaFDh+K1115DcnIyvvzySzzzzDM4deoUHBwckJubi969eyMnJwdTpkyBu7s74uLi8PvvvyM9PR329vb4v//7P7z22mvo0KEDJk6cCABo3LhxsXG5ubmha9eu2LRpE+bOnauzbePGjVAoFBgyZAgA4Nq1a/jll18wZMgQ+Pn5ITExEV999RW6du2KCxcuwNPTs8K/n8zMTHzzzTcYPnw4JkyYgLt37yI8PBy9e/fG8ePH0aZNm3If87///kOXLl1gbm6OiRMnwtfXF1evXsVvv/2GBQsWAADy8vK0yV1ZHB0dtf0vTp06hSeffLJIf4wOHTpgzZo1uHz5MgICAvSOdefOnUhPT9crSVi3bh18fHzwzDPP6H18fY5pZWWlk3SV5MiRIwCAJ598ssi2//u//8PPP/+MY8eOoUWLFgAAIQSysrJQr149AFLC/uqrr8LZ2RlDhgxBnz590LBhQwBS0vzUU09BJpNh8uTJcHFxwa5duzB+/HhkZmZi2rRpOudbsGABZDIZZsyYgaSkJISGhqJnz544ffq0NmH77rvvMHbsWLRv3x4hISFITEzEsmXLcPjwYe3/FaDf60Vj6NCh8PPzQ0hICE6ePIlvvvkGrq6u+PTTT/X/pZfi1KlTAIB27drprPf09IS3t7d2u4a9vT0aN26Mw4cPY/r06VUSA1UBI9fkEJVJUz384osv6qyfNGmSACDOnDkjhBDi+vXrQqFQiAULFuiUO3v2rDAzM9OuP3XqVJFmhuKUp7nnq6++EgDE2bNnddY//vjj4tlnn9U+z87OFiqVSqdMTEyMUCqVYt68eTrrUM7mnvz8fJGTk6Oz7s6dO8LNzU2MGzdOu27//v0CgNi/f3+ROAqf85lnnhG2trbixo0bOmXVanWR4+mzFKyWt7Gx0YlLY8eOHQKA2L17t97XLoQQgwYNEkqlUty5c6fUcufOnRMAxHvvvVdqufI096SmpgoLCwsxdOhQvcp/+OGHAoC4e/euznq1Wi38/PxEaGiodt2vv/4qPD09BQDRoEED8ccff+j8LgcMGCDef/99bfnx48cLDw8PkZKSonPsYcOGCXt7e21zlubv5uXlpW0+EUKITZs2CQBi2bJlQgghcnNzhaurq2jZsqV48OCBttzvv/8uAIg5c+Zo1+nzetH8Pxf+2w8YMEA4OTmV/csroLTmHs222NjYItvat28vnnrqqSLrn3vuOdG8efNyxUCGxeYeqjGCgoJ0nk+ZMgWA9A0aALZt2wa1Wo2hQ4ciJSVFu7i7u6NJkybaZg97e3sAwB9//KFX9bI+Bg4cCDMzM2zcuFG77ty5c7hw4QJefvll7TqlUqmtOVCpVEhNTUW9evXQtGlTvUe1lEShUMDCwgKA1Ck1LS0N+fn5aNeuXYWOnZycjH/++Qfjxo1DgwYNdLYVHPXSunVr7NmzR6/F3d1du9+DBw+gVCqLnNfS0lK7XV+ZmZnYsWMHXnjhBe23+pJoRt7oU+Oiry1btiA3N1fvY6ampsLMzExbM6IRGRmJpKQkjB8/HoDUbDN8+HB06NABW7duxfTp0zFu3Didffr3769tuhNCYOvWrejXrx+EEDr/B71790ZGRkaR18KoUaN0mk8GDx4MDw8P7f/Vv//+i6SkJEyaNEn7twGAvn37olmzZtixYwcA/V8vGm+88YbO8y5duiA1NRWZmZll/v70oXn9lPQaK+71Vb9+fZMaGk1s7qEapPBoj8aNG0Mul2uHH165cgVCiBJHhZibmwMA/Pz8EBwcjKVLl2LdunXo0qULXnzxRYwcOVKbwJSXs7MzevTogU2bNmH+/PkApKYeMzMznep/tVqNZcuWYdWqVYiJiYFKpdJuc3JyqtC5C/r+++/x+eef49KlSzrDVf38/Mp9rGvXrgEAWrZsWWq5+vXrl9i3ojRWVlbF9jvR9DMqT9+QrVu3Ijs7u8wkQQiB9evXo2XLlmjVqlX5Ai7FunXr4OjoiD59+lTqOJGRkWjXrp02eVm3bh28vLywZcsWKBQKAICDgwPGjh2r3cfNzQ3JyckApEQhPT0da9aswZo1a4o9R1JSks7zwv8vMpkM/v7+2v+rGzduAACaNm1a5FjNmjXDoUOHAOj/etEonMjUr18fAHDnzh3Y2dnpdYzSaF4/Jb3Gint9CSFKHHZOxsEkhWqswm8marUaMpkMu3bt0r6hF1TwW+vnn3+OMWPG4Ndff8Wff/6Jt956S9vXpaxOlyUZNmwYxo4di9OnT6NNmzbYtGkTevToAWdnZ22ZhQsXYvbs2Rg3bhzmz5+v7aMxbdq0Sk/o9eOPP2LMmDHo378/3n33Xbi6ukKhUCAkJARXr17VlivpTbhgwlQeubm5SEtL06usi4uL9m/j4eGB+Pj4ImU068rTP2fdunWwt7fH//73v1LLHT58GDdu3EBISIjexy5LbGwsDh48iIkTJ2oT4bI4OTkhPz8fd+/e1anFSE1N1bnu69ev44knntB5PXfo0EHnWDdv3tQmuJrX0MiRIzF69Ohiz12VyVllFPc/CkiJQlXw8PAAIL2efHx8dLbFx8cX+T0CUoJU8P+VjI9JCtUYV65c0akRiI6Ohlqthq+vLwCpZkUIAT8/Pzz22GNlHi8gIAABAQH48MMPceTIEXTu3BlhYWH45JNPAJT8YV6S/v374/XXX9c2+Vy+fBmzZs3SKbNlyxZ0794d4eHhOuvT09Mr/ea4ZcsWNGrUCNu2bdOJvXBnXs031vT0dJ31mm/MGo0aNQIgNVuV5siRI+jevbteMcbExGj/Xm3atMHBgwehVqt1Os8eO3YM1tbWev0NAekDZ//+/RgzZkyxVfsFrVu3DjKZDK+88opex9ZHSSOFStOsWTMA0u+jYNJgZ2en0wnZ3d0dx48f19lXU2MBSB/o4eHh2posFxcX2NraQqVS6V27deXKFZ3nQghER0dr49J0yI2KisKzzz6rUzYqKkq7Xd/XS3XRdBT/999/dRKS27dv49atW9oO8QXFxMSgdevW1RUi6YF9UqjGWLlypc7zL7/8EgC0VewDBw6EQqHAxx9/XOTbmBACqampAKT+C4VnnAwICIBcLtepGraxsSnyQV4aBwcH9O7dG5s2bcKGDRtgYWFRZCI4hUJRJLbNmzcXO5SzvDTfTAse/9ixY4iIiNAp17BhQygUCvzzzz8661etWqXz3MXFBc888wzWrl2L2NhYnW0Fz1HRPimDBw9GYmIitm3bpl2XkpKCzZs3o1+/fjoJx9WrV3VqgwrasGED1Gp1mUlCXl4eNm/ejKeffrpIU0NlrF+/Hg0aNMDTTz+t9z4dO3YEIH2AFtS8eXOcOHFCWyPy0ksv4dSpU5gzZw6uXbuGgwcP4t133wUgjV4ZNGgQbt26halTpwKQXgODBg3C1q1bi00WNM1CBf3www+4e/eu9vmWLVsQHx+v/b9q164dXF1dERYWpvP/sWvXLly8eBF9+/YFoP/rpbq0aNECzZo1w5o1a3RqCVevXg2ZTIbBgwfrlM/IyMDVq1fRqVOn6g6VSlP9fXWJykczGiAgIED069dPrFy5UowcOVIAEK+88opO2ZCQEAFAdOrUSSxevFisXr1avPfee6JJkyZiyZIlQgghfv75Z+Hl5SWmTZsmVq1aJZYvXy7at28vzM3NRUREhPZYL7zwgrCxsRGff/65+Omnn8TRo0fLjPXHH38UAIStra3o169fke1z5swRAMSYMWPEmjVrxJQpU4Sjo6No1KiR6Nq1q7ZcRUb3rF27VjsK6quvvhIzZ84UDg4OokWLFqJhw4Y6ZYcNGybMzMxEcHCwWLlypejTp49o27ZtkXOePn1a1KtXTzg5OYlZs2aJNWvWiPfff1+0bt1a77hKkp+fL5566ilRr1498fHHH4uVK1eKFi1aCFtbW3Hp0iWdsg0bNixyDRpt27YVnp6eRUZNFfbbb78JACIsLKzEMtevXxfz588X8+fPF4GBgQKA9vkPP/xQpPzZs2cFADFz5syyL7iQli1bFpm0Ljs7W9jb24uff/5Zu27hwoVCLpcLAMLMzEwsW7ZMO1rqueeeE9euXdM5RkJCgmjYsKGwtrYWU6dOFV999ZUICQkRQ4YMEfXr19eW04zuCQgIEK1atRJffPGFmDlzprC0tBT+/v4iKytLW/bbb78VAERgYKAIDQ0Vs2bNEtbW1sLX11dnNJU+r5eSJnPTnKOkidk00tPTtX+T559/XgAQb7/9tpg/f7748ssvdcr+9ttvQiaTiWeffVasWbNGvPXWW0IulxeZxE8IaTI3ACI6OrrU81P1YpJCJk/zpnbhwgUxePBgYWtrK+rXry8mT56sMyRSY+vWreLpp58WNjY2wsbGRjRr1kwEBQWJqKgoIYQQ165dE+PGjRONGzcWlpaWwtHRUXTv3l3s3btX5ziXLl0SzzzzjLCyshIA9BqOnJmZqS3/448/FtmenZ0t3n77beHh4SGsrKxE586dRUREhOjatWulkxS1Wi0WLlwoGjZsKJRKpXjiiSfE77//LkaPHl3kAz45OVkMGjRIWFtbi/r164vXX39dOzS38DnPnTsnBgwYIBwcHISlpaVo2rSpmD17tt5xlSYtLU2MHz9eODk5CWtra9G1a1dx4sSJIuVKSlIuXbokAIjg4OAyzzVs2DBhbm4uUlNTSyxT2nDqgn8fjZkzZwoA4r///ivz/IUtXbpU1KtXTzskWGPu3LmiUaNGIi0tTbsuLi5O/PPPPyIhIUEIIcShQ4dEUlJSicdOTEwUQUFBwsfHR5ibmwt3d3fRo0cPsWbNmiLX+tNPP4lZs2YJV1dXYWVlJfr27VtkCLEQ0gzBTzzxhFAqlcLR0VGMGDFC3Lp1q0i5sl4vlU1SNP8bxS3FvUZ+/vln0aZNG6FUKoW3t7f48MMPRW5ubpFyL7/8snj66adLPTdVP5kQRqiHIyKq4zIyMtCoUSMsXrxYO+QYkEaedO7cGQqFAr/++qu2A2hhW7ZswYABA0rsgFqWAwcOoHv37ti8eXORpo+6JiEhAX5+ftiwYQNeeuklY4dDBbBPChGREdjb2+O9997DkiVLdEZ2WVpaYufOnZDJZGjatClmzJiBf/75Bzdu3MClS5fwww8/oGPHjhg9enSl59YhSWhoKAICApigmCDWpBCZOH2G+Nrb21f4njNkmnJzc7FixQqsWLECMTEx2vWWlpYYMGAAPv744zLvFF0a1qRQTcAhyEQmTp8hvt9++22JN0CkmsnCwgLBwcEIDg7G9evXERcXB0tLSzRv3hzW1tbGDo+oWrAmhcjE3blzB5GRkaWWadGiRYl9F4iIaiomKURERGSS2HGWiIiITBL7pFSQWq3G7du3YWtryxtSERERlYMQAnfv3oWnp6fObTEKY5JSQbdv3y5y0yoiIiLS382bN0u9qSuTlArS3Ln05s2bVXJbcSIioroiMzMTPj4+OncBLw6TlArSNPHY2dkxSSEiIqqAsrpLsOMsERERmSQmKURERGSSmKQQERGRSWKfFAMSQiA/Px8qlcrYoVA5KRQKmJmZcXg5EZERMUkxkNzcXMTHx+P+/fvGDoUqyNraGh4eHrCwsDB2KEREdRKTFANQq9WIiYmBQqGAp6cnLCws+I28BhFCIDc3F8nJyYiJiUGTJk1KnWyIiIgMg0mKAeTm5kKtVsPHx4d3K62hrKysYG5ujhs3biA3NxeWlpbGDomIqNqpVMDBg0B8PODhAXTpAigU1Xd+JikGxG/fNRv/fkRUl23bBkydCty69WidtzewbBkwcGD1xMB3YSIiItKxbRsweLBuggIAcXHS+m3bqicOJilERESkpVJJNShCFN2mWTdtmlTO0JikmDCVCjhwAPjpJ+lnTRvJ7Ovri9DQUGOHQUREpVCrgfz8R89DQorWoBQkBHDzptRXxdDYJ8VEGastsFu3bmjTpk2VJBcnTpyAjY1N5YMiIqrDqqrzqhDA7dvAuXPA+fPScu4ccOECsG4d8OKLUrnUVP2OFx9f/hjKi0mKCdK0BRauatO0BW7ZUn2dlgoTQkClUsHMrOyXjouLSzVERERUe1X0C2tSEmBmBjg6Ss///BMYOhTIyCi+/Pnzj5KUrl0Bfb6nenjodQmVwuaeapSVVfKSnS2V0actcOpU3aafko5ZXmPGjMHff/+NZcuWQSaTQSaT4bvvvoNMJsOuXbvQtm1bKJVKHDp0CFevXsVLL70ENzc31KtXD+3bt8fevXt1jle4uUcmk+Gbb77BgAEDYG1tjSZNmmD79u16xaZSqTB+/Hj4+fnBysoKTZs2xbJly3TKdOvWDdOmTdNZ179/f4wZM0b7PCcnBzNmzICPjw+USiX8/f0RHh5ert8TEVF10Kfz6p07wKFDQFgYMGUK0L074OoKuLkB33//aB8PDylBUSiAZs2AQYOAuXOBTZukBOWddx6V7ddPSoRKmt5LJgN8fKQaHUNjTUo1qlev5G0vvADs2CFV6ZXVFnjrllSuWzdpna8vkJJSfNnyWLZsGS5fvoyWLVti3rx5AIDz588DAGbOnInPPvsMjRo1Qv369XHz5k288MILWLBgAZRKJX744Qf069cPUVFRaNCgQYnn+Pjjj7F48WIsWbIEX375JUaMGIEbN27AUZPul0CtVsPb2xubN2+Gk5MTjhw5gokTJ8LDwwNDhw7V+xpHjRqFiIgILF++HK1bt0ZMTAxSivvlEREZUVlfWGUyYNIkIDGx+P1lMiAh4dHzZs2AM2eApk0BpbL0cysUUk3N4MHScQrGoElcQkOrZ74UJikmRt82PkO0Bdrb28PCwgLW1tZwd3cHAFy6dAkAMG/ePPTq1Utb1tHREa1bt9Y+nz9/Pn7++Wds374dkydPLvEcY8aMwfDhwwEACxcuxPLly3H8+HE8//zzpcZmbm6Ojz/+WPvcz88PERER2LRpk95JyuXLl7Fp0ybs2bMHPXv2BAA0atRIr32JiKpLQgLwww9lf2HVJCgNGgAtWgAtWz762bw5UHAuUXNzoFUr/WMYOFDqWlBcU1NoaPV1OWCSUo3u3St5myYj1beNr2C569crHJLe2rVrp/P83r17+Oijj7Bjxw7Ex8cjPz8fDx48QGxsbKnHaVXgv8TGxgZ2dnZISkrSK4aVK1di7dq1iI2NxYMHD5Cbm4s2bdrofQ2nT5+GQqFA165d9d6HiMhQVCrg8mUgNxfQfOdLSSlfX4/wcGDcOMPEN3Ag8NJLnHG2ztBnoEuXLlKmGhdXfDWfTCZtL9gWWB0DaAqP0nnnnXewZ88efPbZZ/D394eVlRUGDx6M3NzcUo9jbm6u81wmk0GtVpd5/g0bNuCdd97B559/jo4dO8LW1hZLlizBsWPHtGXkcjlEoV9aXl6e9rGVlVWZ5yEiKkllRtncuwecPQucPv1oOXsWePAAeO454I8/pHLOzlLNCACU8Z0PAGDoymCF4lHXAmNgkmJijN0WaGFhAZUeE7IcPnwYY8aMwYABAwBINSvXDVilc/jwYXTq1AmTJk3Srrt69apOGRcXF8QXaAdTqVQ4d+4cunfvDgAICAiAWq3G33//rW3uISLSh76jbISQkpiEBODJJx+ta9gQSEsrelwbG6DwrcGiowG5XOpvWJ4vrLURR/eYIE1boJeX7npvb8MPP/b19cWxY8dw/fp1pKSklFjL0aRJE2zbtg2nT5/GmTNn8Morr+hVI1JRTZo0wb///os//vgDly9fxuzZs3HixAmdMs8++yx27NiBHTt24NKlS3jzzTeRnp6uc22jR4/GuHHj8MsvvyAmJgYHDhzApk2bDBY3EdV8ZY2ymT4deO89qUbEzU167y7YVU4mAwICAE9PaZDE++9Lo2qioqQRN7/+qntcc/NHX1g1+xdU3Z1XjYk1KSbKWG2B77zzDkaPHo3HH38cDx48wLfffltsuaVLl2LcuHHo1KkTnJ2dMWPGDGRmZhosrtdffx2nTp3Cyy+/DJlMhuHDh2PSpEnYtWuXtsy4ceNw5swZjBo1CmZmZpg+fbq2FkVj9erVeP/99zFp0iSkpqaiQYMGeP/99w0WNxHVbPpMC1F4ThG5XKodyc0FLCykdbt2AeVtcTaVzqvGJBOFG/FJL5mZmbC3t0dGRgbs7Ox0tmVnZyMmJgZ+fn6wLFyPRzUG/45EdU9qqu6MrIcOAf/9V/Z+/fsDfftKHWBbtix/QlKaqppx1pSU9hlaEGtSiIioRqmKD+07dx4lIq+99mj/t94C1q8vf0xDhwIPZ1eocsbuvGpMTFLIJLzxxhv48ccfi902cuRIhIWFVXNERGSKKjJNfFSUVCNSsIbk9u1H2599FmjSRHocECB1WNXMOSKXSzfcK0t1TBFfF7G5p4LY3FO1kpKSSuzTYmdnB1dX12qOiH9HIlNT0n3NNB1JFy2SpoQ/fx54+23g4ZyUmDsXeDiJtg4fHykRWbJESkqAR7O5aqhU+o2yiYmp+U0w1YnNPVSjuLq6GiURIaKaQZ8OrDNmPFr37LNAnz7S4w4dgF69pIREszz+OGBvX/RYhUfSGHtaiLqOSQoREZmcvDxpvpBz56Rl//7Sp4nXaNMGePpp3eaXvn2lpaI4ysZ4jD5PysqVK+Hr6wtLS0sEBgbi+PHjpZYPDQ1F06ZNYWVlBR8fH0yfPh3ZmlsI63nM7OxsBAUFwcnJCfXq1cOgQYOQWNJdmoiIqFgqFXDgAPDTT9JPPeaBLNXp08Arr0gjZOrVk2o7hg6VmmoOHtTvGO+9B3z5pZSsVKWBA6VbkOzfL3Ws3b9fauJhgmJgwog2bNggLCwsxNq1a8X58+fFhAkThIODg0hMTCy2/Lp164RSqRTr1q0TMTEx4o8//hAeHh5i+vTp5TrmG2+8IXx8fMS+ffvEv//+K5566inRqVOncsWekZEhAIiMjIwi2x48eCAuXLggHjx4UK5jkmnh35GoZFu3CuHtLYTUACIt3t7S+uKo1ULcvCnErl1CfPaZEGPGCNGunRA//PCozJEjuserV0+IwEAhxo8XIihId1tJy/791XL5VEmlfYYWZNQkpUOHDiIoKEj7XKVSCU9PTxESElJs+aCgIPHss8/qrAsODhadO3fW+5jp6enC3NxcbN68WVvm4sWLAoCIiIjQO3YmKbUf/45Exdu6VQiZrGiCIJNJy6ZNj8peuiREp05C2NsXn1RMnfqobGamEIsWCfH770Jcvy6ESvVoW36+lAQVd17NuX18pHJk+vRNUozW3JObm4vIyEide6jI5XL07NkTERERxe7TqVMnREZGaptvrl27hp07d+KFF17Q+5iRkZHIy8vTKdOsWTM0aNCgxPMCQE5ODjIzM3UWIqK6pqwOrEIA48c/avqpXx84ckSa/l2hkJpwhgwBPv4Y2LpVmlJew9ZW6vzat690rxt5gU8oThNfNxmt42xKSgpUKhXc3Nx01ru5ueHSpUvF7vPKK68gJSUFTz/9NIQQyM/PxxtvvKGd1lyfYyYkJMDCwgIODg5FyiQkJJQYb0hICD7++OPyXmalqITAwfR0xOfmwsPCAl0cHKAo/N9pYnx9fTFt2jRMmzbN2KEQURURQrpbr7W11DekrA6sd+9K5bp1k4YEb9wING8OPPYYoFRWPA52YK17atTongMHDmDhwoVYtWoVAgMDER0djalTp2L+/PmYPXu2Qc89a9YsBAcHa59nZmbCx8fHYOfblpyMqdHRuJWTo13nrVRimb8/Brq4GOy8RFTzVOW06Xl50uRnp08/Ws6ckRKOzZulc+ijYLmCN9urLGPd14yMw2hJirOzMxQKRZFRNYmJiXDXzMBTyOzZs/Hqq6/itddeAwAEBAQgKysLEydOxAcffKDXMd3d3ZGbm4v09HSd2pTSzgsASqUSysp8BSiHbcnJGHz+PArXpsbl5GDw+fPY0qIFExUiAlCxGVg1Ct4ATwjpw/7ff4EC3420NPev0XdmVUPOwFqXp4mva4zWJ8XCwgJt27bFvn37tOvUajX27duHjh07FrvP/fv3IZfrhqx4mD4LIfQ6Ztu2bWFubq5TJioqCrGxsSWet7KEEMhSqfRaMvPz8daVK0USFADadVOjo5GZn6/X8UQ5JhRes2YNPD09oVardda/9NJLGDduHK5evYqXXnoJbm5uqFevHtq3b4+9e/dW+PeydOlSBAQEwMbGBj4+Ppg0aRLu3bun3f7RRx+hTaFxhKGhofD19dVZt3btWrRo0QJKpRIeHh6YPHlyhWMiqkk0M7AWbn6Ji5PWb9smPRcCiI0Ftm+XhvMOGgQ0bgwEBj7aRyaTmnRycqS+IZ07A0FBwNdfAydOSDUqgJTIeHsX7RdS8Dg+PlI5osoyanNPcHAwRo8ejXbt2qFDhw4IDQ1FVlYWxo4dCwAYNWoUvLy8EPLwxgn9+vXD0qVL8cQTT2ibe2bPno1+/fppk5Wyjmlvb4/x48cjODgYjo6OsLOzw5QpU9CxY0c89dRTBrnO+2o16uk7yL8MAsCtnBzYHzqkV/l7XbrARs960CFDhmDKlCnYv38/evToAQBIS0vD7t27sXPnTty7dw8vvPACFixYAKVSiR9++AH9+vVDVFQUGjRoUO5rkcvlWL58Ofz8/HDt2jVMmjQJ7733HlatWqX3MVavXo3g4GAsWrQIffr0QUZGBg4fPlzuWIhqmrI6sMpkwLRpwK+/Ar/9Jt1QrzBzc93alDVrAAcHwM9Pt9NqQZyBlaqTUZOUl19+GcnJyZgzZw4SEhLQpk0b7N69W9vxNTY2Vqfm5MMPP4RMJsOHH36IuLg4uLi4oF+/fliwYIHexwSAL774AnK5HIMGDUJOTg569+5drg/G2qp+/fro06cP1q9fr01StmzZAmdnZ3Tv3h1yuRytW7fWlp8/fz5+/vlnbN++vUK1FwU71/r6+uKTTz7BG2+8Ua6/xSeffIK3334bU6dO1a5r3759uWMhqmnK6sAqBHDzprTcuQOYmUkja9q0ebS0bv0oQQGAtm31Ozc7sFJ1MXrH2cmTJ5f4AXfgwAGd52ZmZpg7dy7mzp1b4WMCgKWlJVauXImVK1eWO96KsJbLcU/Pus9/0tPxwtmzZZbbGRCAZwqNUCrp3OUxYsQITJgwAatWrYJSqcS6deswbNgwyOVy3Lt3Dx999BF27NiB+Ph45Ofn48GDB4iNjS3XOTT27t2LkJAQXLp0CZmZmcjPz0d2djbu378Pa2vrMvdPSkrC7du3tQkVUV2xeLGUDOijd29g6VJpdE1VdqtjB1aqDkZPUuoCmUymd5PLc46O8FYqEZeTU2y/FBmkUT7POToaZDhyv379IITAjh070L59exw8eBBffPEFAOCdd97Bnj178Nlnn8Hf3x9WVlYYPHgwcnNzy32e69ev43//+x/efPNNLFiwAI6Ojjh06BDGjx+P3NxcWFtbQy6XF+lTk5eXp31sZWVVuYslqkJVOcImKQk4eVJ3iYgANBXCGRn6j7IJDKz6KeI12IGVDI1JiolRyGRY5u+PwefPQwboJCqalCTU399g86VYWlpi4MCBWLduHaKjo9G0aVM8+eSTAIDDhw9jzJgxGDBgAADg3r17uH79eoXOExkZCbVajc8//1zbpLdp0yadMi4uLkhISIAQArKH13ta03sPgK2tLXx9fbFv3z507969QnEQVYXKjLDR2LULWL1aSkji4opuP3UKeP556fHIkUCnTsCECUBCQvH9UmQyKQZ2YKWajEmKCRro4oItLVoUO09KaDXMkzJixAj873//w/nz5zFy5Ejt+iZNmmDbtm3o168fZDIZZs+eXWQkkL78/f2Rl5eHL7/8Ev369cPhw4cRFhamU6Zbt25ITk7G4sWLMXjwYOzevRu7du2CnZ2dtsxHH32EN954A66urujTpw/u3r2Lw4cPY8qUKRW7eKJy0oywKZwoaEbYbNkiJSpCSDekK1g78sknQLt2UvnERKmDKyAlGE2bAk8++Wgp2NWqeXNpWbGCHVipljP0/Py1VXXcuydfrRb709LE+oQEsT8tTeSr1ZU6nr5UKpXw8PAQAMTVq1e162NiYkT37t2FlZWV8PHxEStWrBBdu3YVUwvcfKNhw4biiy++0Os8S5cuFR4eHsLKykr07t1b/PDDDwKAuHPnjrbM6tWrhY+Pj7CxsRGjRo0SCxYsEA0bNtQ5TlhYmGjatKkwNzcXHh4eYsqUKZW4+kd47x4qi+Z+MiXd7E4mE8LWVohu3Yq/d82XXz46VkyMEMuXC3HokBB37+ofQ3E3+vPxKflGf0SmQN9798iEKMdEGqSVmZkJe3t7ZGRk6HyzB4Ds7GzExMTAz88PlpaWRoqQKot/RyrLgQNAeVoaLSyAVq2AJ56Qakd69gT8/SsfR1X2hyGqDqV9hhbE5h4ionLKzwciI6U+JPqYOBGYNElqoik45LeqsAMr1VZMUsgg1q1bh9dff73YbQ0bNsT58+erOSKiqpGcDDRpIo2w0dfw4dKcJERUPkxSyCBefPFFBBacc7sAc3Pzao6GqPxiY4F9+6TFxgb46itpvYsL4OgodU7t1k1q8snI4AgbIkNgkkIGYWtrC1tbW2OHQXVQRftnpKQA+/c/Skyiox9ts7cHVq6UZm0FpMTEy0s6rmZ0D0fYEFU9JikGxD7JNRv/fjVPeeYryc4GCvaHHjhQSm40FApp2G+PHtJScGqigreq4hTxRIbDJMUANM0Z9+/f56yoNdj9+/cBsHmqpihrvpINGwBPz0c1JcePA7dvS003gJSIpKVJI2569AC6dgVKGXSgg1PEExkGhyBXUFnDp+Lj45Geng5XV1dYW1trZ0wl0yeEwP3795GUlAQHBwd4eHgYOyQqg0oF+PqWfsO9ws0xALB9O9Cvn/RYrS75zr9EVLU4BNnI3N3dAUg3waOaycHBQft3JNNW1h2BASlBsbOTppbXNOE0avRoOxMUItPDJMVAZDIZPDw84OrqqnNTPKoZzM3NoWBdvcm7c0fq7LpqlX7lV60CRowwbExEVHWYpBiYQqHghx1RFUpLA5YuBfbsAf79V2qm0ZeXl+HiIqKqxwpOIjJZQgBnz+qOulEqgcWLpY6vajXQrBkQFAQ4OemOwClIJgN8fDhfCVFNw5oUIjKIis5XcusWsHfvoyUxUbrPTWSktN3GBpgzRxri27On9BMAnn2W85UQ1TZMUoioypVnvhKNjz+WhglfuqS73toacHeX7pejmUztww+L7s/5SohqHyYpRFSl9JmvxMtLqmWZMeNRTcfly1KCIpcD7doBvXpJNSUdO0pNPPrgfCVEtQvnSakgfcd4E9Ul5Z2v5L//gIAA6XFEhJRYdO8O1K9v8FCJyIg4TwoRVbvyzldSUMeOhouLiGomJilEVCVSUqSmHH1wvhIi0geTFCKqsKtXgV9/lZZDh/Sfs4TzlRCRPpikEFG5PHgALFggJSbnzulua90auHEDyMgo2nEWkPqjeHtzvhIi0g8ncyOiUuXk6CYjSiXw7bfSOoVCmp9k2TIgJgY4fRoID5fKFZ5YjfOVEFF5MUkhoiLS04H164GXXwZcXKRERKWStsnl0pwm//d/QFISsG8f8NZb0qge4NF8JYWbdLy9pfWcr8RATp8G+vSRfhLVEiaRpKxcuRK+vr6wtLREYGAgjh8/XmLZbt26QSaTFVn69u2rLVPcdplMhiVLlmjL+Pr6Ftm+aNEig14nkTGoVMCBA8BPP0k/NclGYbduAStWSPOTuLhIHVs3bQLu3pVqPq5ff1T2tdeAkSMBR8fijzVwoFR+/34p2dm/X6ppYYJiQFu3Art3SxPVENUSRu+TsnHjRgQHByMsLAyBgYEIDQ1F7969ERUVBVdX1yLlt23bhtzcXO3z1NRUtG7dGkOGDNGui4+P19ln165dGD9+PAYNGqSzft68eZgwYYL2ua2tbVVdFpFJKG3m1wEDpH4j8odfVVauBArm6c2bSxOj9e8PtG//qJy+FAqgW7fKXgHp7bffHv2cN8+4sRBVEaMnKUuXLsWECRMwduxYAEBYWBh27NiBtWvXYubMmUXKOxb66rZhwwZYW1vrJCnu7u46ZX799Vd0794djRo10llva2tbpCxRbVHSzK+3bgGDBkm1Jd9+C2gqIfv3l+Y5eeklaXnssWoPmSoqMRE4c0Z6fPq01A5XzJc8oprGqM09ubm5iIyMRM+ePbXr5HI5evbsiYiICL2OER4ejmHDhsHGxqbY7YmJidixYwfGjx9fZNuiRYvg5OSEJ554AkuWLEF+fn6J58nJyUFmZqbOQmSqVCqpBqW0+aSTk4Fffnn0PDBQGkb87rtMUGqcP/4o/TlRDWXUJCUlJQUqlQpubm46693c3JCQkFDm/sePH8e5c+fw2muvlVjm+++/h62tLQYWagx/6623sGHDBuzfvx+vv/46Fi5ciPfee6/E44SEhMDe3l67+Pj4lBkfkbHoM/MrwD4itYV6xw6oHw6ZUisUUO/YYeSIiKqG0Zt7KiM8PBwBAQHo0KFDiWXWrl2LESNGwNLSUmd9cHCw9nGrVq1gYWGB119/HSEhIVAWczezWbNm6eyTmZnJRIVMTnQ0cPYskJ2tX/n0dIOGQ1UlLk5q0inGX2lpaP/777B92CNarlIh8/ff8e/evXi2pJ7Nbm6cUY/0ohICB9PTEZ+bCw8LC3RxcICi8PwCBmTUJMXZ2RkKhQKJhf75EhMTy+wrkpWVhQ0bNmBeKR3EDh48iKioKGzcuLHMWAIDA5Gfn4/r16+jadOmRbYrlcpikxciY0tNlUbh/PADcPQoUK8esHmzfvt6eBg2trrEoG/mo0YBf/1V7KZnAagLnafe/ft4tlevko/Xowewd2/VxGYExv7grCu2JSdjanQ0buXkaNd5K5VY5u+PgS4u1RKDUZMUCwsLtG3bFvv27UP//v0BAGq1Gvv27cPkyZNL3Xfz5s3IycnByJEjSywTHh6Otm3bonXr1mXGcvr0acjl8mJHFBGZmpwcYMcOaa6SHTuAvDxpvVwOdO4s9Snx9pa+gHPmV8Mz+Jv5G28AJ0+WWPUlL/RHLvxch4MD8PrrlY8JxkkWjPnBWZeSo23JyRh8/jwKv5LicnIw+Px5bGnRoloSFaM39wQHB2P06NFo164dOnTogNDQUGRlZWlH+4waNQpeXl4ICQnR2S88PBz9+/eHk5NTscfNzMzE5s2b8fnnnxfZFhERgWPHjqF79+6wtbVFREQEpk+fjpEjR6I+7xFPNcBnnwEffvjoeZs2wKuvAsOHP6odWbZMGt0jk+kmKpz5tWpV9Zt5lkqFpNxcJOflSUtuLpIDA5H1xx/43wcfoN3evVDLZKUnIoUImQwyIZDerx/Uq1ejvqcnKvvRaoxkwZgfnHUpOVIJganR0UV+zwAgAMgATIuOxkvOzgZP0mRClOOVbiArVqzAkiVLkJCQgDZt2mD58uUIDAwEIE3e5uvri++++05bPioqCs2aNcOff/6JXiVUaa5ZswbTpk1DfHw87O3tdbadPHkSkyZNwqVLl5CTkwM/Pz+8+uqrCA4O1rtJJzMzE/b29sjIyICdnV3FLpxID9HRUo1J587Ac89J665eleYgeeUVKTlp2bL4fbdtA96aJhDnmA445QKpFvC+44BlX8hqbafZ6nxDVwkB36NHdT64CpIB8LSwwP42bZCqSToKJB9JBRORh48flHGXxiH79yNs6VLY3b8PMz3u6JgvlyPT2hpvBAdjc/fuAABruRwNLC3RQKnU/vQp8NhbqYRlKRlsScmC5rdsiGRBn9+1t1KJmKeeqvK/tzGut+C5DZUc5avVSM7LQ1JeHhJzc5H48DV5IjMTG5OTy9x/f+vW6FbBL/b6foaaRJJSEzFJIUMq3M8EkOYuKThkWK0ue4K1bcnJmHolGrdyC7zBWSixrEn1tSlXp+r4tputUiEpLw9JubnYe+cOZsXEVMlxC7KUy+Fibv5osbCAa4HHXunpaD5pEhocOFBqjYgAcOrpp/HlJ5/gXL16iM3ORpKmbbAMbubmjxKYAgmNl4UFBp4/j/gCk2oWVFKyIIRAvhDIUauRXcySI0TRdQUeX8jKQrgeoz6Hu7qisZUVzGUymBVYCj8vcZ1crvNcBuDFs2eRWMLvzdSSowcqlZRwPHyNahKPxGIep5Yy7YY+1jdvjuGFRufqi0mKgTFJofJSqaShwfHxUpNMly66zS1CAD//XHw/k169gLFjpXvp6MuY3/6A6q+iruj1qoRA6sM39CQ9ft4t6b4CpbCQyeBhYQEXCwtt4uFa4LEm+XAxN4eruTlsFArIyvhdqT/8EOpFi2BWSjz5CgXks2ZBPn++dl22SoVbOTmIzclBbHa2zs+bOTm4kZ1dZm2OPjzMzSGTyXSSjsof1XSZy2SwlsthKZdDWdJPmUzneWllzGUyvHftGtJKSSRs5HL0rF9f+/pMzMvDvXK+PuWA9vXo9jAZzlWrsSUlpcx9WZNiwpikUHmUNj19wWaXDh2AEyekx8X1M9GXMavGgepvvy/regHAXqHAq25uSMnP10k8UvLyyv3haSGTwdXCAlYyGa7oMd67Mm/mJWrTBuLMmTJrUmRt2gCnTul9WCEE0vLziyYwD39eysrCnQokaoWZP/wwLrwoi1lnKZcjLS8PO9PSyjzuYGdnuCuVyH9Yc5MvBPLUap3n+UIgr9DzktZl5OeXmiiYIqVMBrcCSYebhYWUhBR67GphASdz8yLvAZr/p7icnGL7pVTF+weTFANjkkL6Kml6eo3vv5dGmALAjz9K85yU1s9EHwfu3EF3zTTppRjn5obWtrawUyhgb2YGOzOzR48VCtiZmcFKLi/zW31BVVGDo3r44XAnPx938vKkn6U8j83O1itZKI2TmRlcH76puxZ6cy+4ztXCAnYPazqq4828WAkJRTJXTWfaYjvVJiRIc6NUAX1fWyv8/dHJ3r7ERERezt+HsX7X+l7vhubN8YStrbaJKqeYpqzC23JK2X7twQOczsoq87xj3d3xPycnnYTEVo+auLJo/o8B6Py+q6omlkmKgTFJIX2oVICvb+mzv9avL01RXxUjbYQQOJuVhfnXr+tVXasPM5lMm7AUTmB0HisUqKdQ4J2rV0tt665vZoa3vb2RoVKVmHRkVME39eK86OSE7g4ORRIPZ3NzmJX3DooPGfrNvFjffw+MGaN9KhQK5Nerh0vjx6NZeDjM7t2DrODvsGAmXElGS8xgnN+1qSdHBqmle6i4GlEfpRKhVVAjyiTFwJikkD4OHAAeDqgo1f79Fb9jcL5ajUMZGfglJQW/pqbiejlqFPo6OsJGoUCmSoXM/Hxk5OdrH2eqVMW+KVcna7kc9c3MUN/cXPqpWQo9v5mTg/f16MBqqDd0Q76ZF+vll4EtW6TqOSGkW1qHhUk3FUxKkuZV+flnaby5TAYMGQJs2FBlpzdKYlbg3NX6u0bdSo6Ki8MQfcuYpBgYkxTSx08/ScOEy7J+vdT3RF/38vPxx507+DUlBTtSU3XazC3lcvR0cMDhzEyk5+dX+A1OLQSyVCpkqlRS8vIwcSmYyOg8VqkQdf8+zupRRf2MvT3a2tqWmHRonlvoWbthCm/o1dZROD8fcHICMjOlidm++goYOrRouU2bpEnb0tMBOzsgLa1KJ8YxRrKgYSqTyNXG5Ki6MEkxMCYpVJqsLMDGpmprUhJycvBbaip+TUnB3jt3kFPgX9fJzAz/c3JCf2dn9HpYO2KMNzhjVlHX5jd0HXfvAs88A/j5Pao9KYmmVuX6deDvvwFb2yoNpS7NwArUneSoOjBJMTAmKVScBw+ApUuBJUuAw4eBZs2kPillTU8fE1P8l9xLWVn49WFicjQzU+fDt5GlJfo7O+MlZ2d0srMrtk9Fdb/BGbtGo7a+oRehUpWvVqS85cmk1MZkkEmKgTFJoYKEkGrX33sPiI2V1k2fLiUs2tE9cgG0TNfO/IpzDpCpZdiy5dEwZLUQOJqZiV9TUvBrSgqiHjzQOU87W1spMXFyQgsbG7168BtrvhLAODUatfENnai2YZJiYExSSOP4cSkhOXJEeu7tDXz6KTBs2KMZYd/7LRlLc6Ohcnr0DV+RqkSwhT8+fsER++7cwa+pqdiekqIzI6i5TIbuDg7o7+yMfk5O8La0rM5Lq7A6U6NBRBXCJMXAmKQQALz1FvDll9Jja2tg5kzg7belxxolzRuiYSGTIbfAv6GdQoEXHvYved7REfZmRr8PaIWwRoOISqLvZ2jNfPcjMhG+vtLPUaOAhQsBLy/d7aXdTVQjVwh4WVho+5d0dXDQe1SLKVPIZAabv4GI6gYmKUR6UqulocIeHkCPHtK6yZOBrl2Btm11ywohcPnBA3wVF1fqVO0a/9e8ObrzA52ISAeTFCI9REQA06ZJ/U+aNpWmrjc3BywsHiUo1x88wF/p6fjrzh3sT0/H7RLuEluchHKUJSKqK5ikEJXixg2pn4lmss569YDRo6ValbicHOx/mJD8lZ5eZKZXC5kMza2tcUaPyc08LCwMET4RUY3GJIXqFH07c967ByxaBHz+OZCdLc1nMuyNXHSblo5TSEfr03eKDA82k8nQ3tYWzzo44Nn69dHRzg4Wcrle84Z0cXAwyPUSEdVkTFKozihuWKy3UollxQyLPXAAWBCaB7TNgNf/7sCqUzp+Umfhp9uPysgAPFmvHp6tXx/dHRzwtL09bIsZibPM3x+Dz5+HDMXPGxLq789RL0RExeAQ5AriEOSapaRhwAUnGHtCVR9R5hnafiUnM+9BFBpkE2Bjg+4Pa0qesbdHfXNzvc/PeUOIiCScJ8XAmKTUHJqp2m9l5zzKSgoSgFwtg1qIInWLj1lZaWtKujk4wLUSfUc4bwgRkYTzpBA9dDA9XarBKCkfkAFqhZSrO6uU6OdVH886OKB7/frwUiqrLA7OG0JEVD5MUqjWi8vWb3jvO5aNseQpHwNHQ0RE+qr501oSlSLy7l18EZWgV1mv7HoGjoaIiMqDNSlU6+Sr1fglJQWht27hcGbmow0CxTf5qAEkK+GidqieAImISC+sSaFaIy0vD4tjY9H42DEMuXABhzMzYSaToSdcgTA/qZC60E5qSInLSn94ebATKxGRKWFNCtV4F7OysDwuDj8kJOC+WspCnM3N8YanJ8a7eGLRO0oc/AXIibcGgqIB1wL30klWAqv84XPdBV26GCd+IiIqHpMUqpHUQuCPtDSE3rqFP+/c0a5vbWODqd7eGO7qitQEBYY8J913BwDwjwtw2BlomQ445QKpFsA5B8jUMoRuARQKo1wKERGVwCSae1auXAlfX19YWloiMDAQx48fL7Fst27dIJPJiix9+/bVlhkzZkyR7c8//7zOcdLS0jBixAjY2dnBwcEB48ePx7179wx2jVQ17uXnY2VcHJofP44Xzp7Fn3fuQAagv7Mz9rdujVPt2mGshweOH1agbVspQXFwAH7/Hdi6FfD2kAFn6gN/uQFn6sPHU4YtW4CBA419ZUREVJjRa1I2btyI4OBghIWFITAwEKGhoejduzeioqLg6upapPy2bduQW+COsampqWjdujWGDBmiU+7555/Ht99+q32uLDTfxYgRIxAfH489e/YgLy8PY8eOxcSJE7F+/foqvkKqCtcfPMCKuDh8Ex+PDJUKAGCnUGC8hwcme3mhkZUVAEAIIDQUeOcdQKUCWrUCtm0DGjeWjvPSS8DBg0B8PODhAXTpwhoUIiJTZfQZZwMDA9G+fXusWLECAKBWq+Hj44MpU6Zg5syZZe4fGhqKOXPmID4+HjY2NgCkmpT09HT88ssvxe5z8eJFPP744zhx4gTatWsHANi9ezdeeOEF3Lp1C56enmWelzPOVo4+s68KIfBPRgaW3bqFX1NStH1em1hZ4S0vL4x2dy9yr5wZM4DFi6XHr7wCfP01YG1dDRdERER6qxEzzubm5iIyMhKzZs3SrpPL5ejZsycitB0JShceHo5hw4ZpExSNAwcOwNXVFfXr18ezzz6LTz75BE5OTgCAiIgIODg4aBMUAOjZsyfkcjmOHTuGAQMGFDlPTk4OcgrcdyWz4NBWKpeybvSXrVJhQ1ISlsXF4XSBJrhe9etjqrc3+jg6Ql7CdPKvvAKEhQHz5wNTpkh3LyYioprJqElKSkoKVCoV3NzcdNa7ubnh0qVLZe5//PhxnDt3DuHh4Trrn3/+eQwcOBB+fn64evUq3n//ffTp0wcRERFQKBRISEgo0pRkZmYGR0dHJCQUP/FXSEgIPv7443JeIRVW0o3+4nJyMPj8eQxyccHf6elIzssDAFjJ5Rjl5oa3vL3xeKFEVOPWLcDbW3rcujUQEwM4OhrwIoiIqFoYvU9KZYSHhyMgIAAdOnTQWT9s2DDt44CAALRq1QqNGzfGgQMH0KNHjwqda9asWQgODtY+z8zMhI8Pp1AvD5UQmBodXSRBAaBdtyU5GYBUszLZywsTPDzgWMKdhtVqYN48YNEi4MAB4KmnpPVMUIiIagejju5xdnaGQqFAYmKizvrExES4u7uXum9WVhY2bNiA8ePHl3meRo0awdnZGdHR0QAAd3d3JCUl6ZTJz89HWlpaiedVKpWws7PTWah8tDf6K8Pchg0RExiIGQ0alJig3LkD9OsHfPwxkJMD7N5d1dESEZGxGTVJsbCwQNu2bbFv3z7tOrVajX379qFjx46l7rt582bk5ORg5MiRZZ7n1q1bSE1NhYeHBwCgY8eOSE9PR2RkpLbMX3/9BbVajcDAwApeDZUlPle/G/01tbaGmbzkl+aZM0C7dsDOnYClJfD998BHH1VRkEREZDKMPk9KcHAwvv76a3z//fe4ePEi3nzzTWRlZWHs2LEAgFGjRul0rNUIDw9H//79tZ1hNe7du4d3330XR48exfXr17Fv3z689NJL8Pf3R+/evQEAzZs3x/PPP48JEybg+PHjOHz4MCZPnoxhw4bpNbKHKsbDwqLS5datAzp2BK5dA3x9gSNHgFGjqihAIiIyKUbvk/Lyyy8jOTkZc+bMQUJCAtq0aYPdu3drO9PGxsZCXuhbdVRUFA4dOoQ///yzyPEUCgX+++8/fP/990hPT4enpyeee+45zJ8/X2eulHXr1mHy5Mno0aMH5HI5Bg0ahOXLlxv2Yuu4Lg4OcDY3R8rDTrGFySD1Reni4FDs9j17AE3FWe/ewPr17H9CRFSbGX2elJqK86SU387UVLx09izyi9mmGSm8pUULDHRxKXZ/IYDBg4HHH5eadzgJGxFRzVQj5kmhuuP3lBQMOn8e+QA62NoiLicHcQX6qHgrlQh9OE9KQcePAy1aADY20pwnmzcDpXRXISKiWoRJChnc9pQUDD5/HnlCYJCzM356/HHIZbJSZ5wVAli1Cpg2DRgyROqLIpMxQSEiqkuYpJBB/ZKcjKEXLiBPCAxxccG65s1h/jDT6Fa/frH73L8PvPEG8H//Jz1XqYDcXKDQ7ZeIiKiW4/dSMpityckY8jBBGebqivUFEpSSXLsGdOokJSgKBfD558CGDUxQiIjqItakkEFsTkrC8AsXoALwiqsrvm/WTGfuE5Wq6N2I9+yR7r1z5w7g4gJs2gR062a0SyAiIiNjkkJVbmNSEkY8TFBedXPDt82a6fQ32bYNmDpVuueOhpcXkJUFpKcDHToAW7c+uh8PERHVTUxSqEr9lJiIkRcvQg1gtJsbwotJUAYPljrGFnT7trSud2/g11/ZvENEROyTQlXox4QEbYIy1t29SIKiUkk1KMXNzCOENHrnwgXAjKkzERGBSQpVkR8SEjDq0iWoAbzm4YFvmjbVSVAAqQ9KwSaewoQAbt6UyhERETFJoUr7Lj4eYy5dggDwuocHvnrsMcgLJSiA1ElWH/qWIyKi2o1JClXK2vh4jIuKggDwpqcnVpWQoADSKB596FuOiIhqNyYpVGFf376N8Q8TlCBPT6xs0qTEBAWQhhm7upZ8PJkM8PGRyhERETFJoQoJi4vDxMuXAQBveXnhyyZNICslQQGk4cUqVfHbNLuGhvLGgUREJGGSQuW2Ki4Ob165AgCY7u2NUH//MhMUQEpAUlOl5hxPT91t3t7Ali3AwIEGCJiIiGokDvakcllx6xamREcDAN729saSxo31SlAAYO5cqZZkwACgZcuiM86yBoWIiAqSCVHcrBVUlszMTNjb2yMjIwN2dnbGDqdaLLt1C9MeJijv+fhgUaNGeicoREREGvp+hrK5h/Sy9OZNbYIyq0EDvROUlBTggw+AnBxDR0hERLUNm3uoTJ/FxuLda9cAAB82bIh5vr56JShqNTBqFLBrl3R3459+MnSkRERUm7AmhUr1aYEEZU45EhQAWLxYSlAsLYH33zdklEREVBuxJoVKtPDGDXwQEwMA+MjXF3N9ffXe9+BB4MMPpccrVgABAQYIkIiIajUmKVSs+devY87169JjX198WI4EJTkZGDZMmhPl1VeBceMMEyMREdVuTFLqOJUQOJiejvjcXHhYWKCLgwM+uXEDHz1MUBb6+WFWw4Z6H0+tlhKT27eBZs2AVaseTdRGRERUHkxS6rBtycmYGh2NWwWG3tgpFMh8OC3sokaNMKNBg3Id8/Jl4OhRwMoK2LwZqFevSkMmIqI6hElKHbUtORmDz59H4UlyNAnKKDe3cicogFR7cuoUcO6cNGEbERFRRTFJqYNUQmBqdHSRBKWg/enpUAkBRQXaavz8pIWIiKgyOAS5DjqYnq7TxFOcmzk5OJiertfx1GpgxAhpuDEREVFVMYkkZeXKlfD19YWlpSUCAwNx/PjxEst269YNMpmsyNK3b18AQF5eHmbMmIGAgADY2NjA09MTo0aNwu3bt3WO4/twvo+Cy6JFiwx6naYiPje3SsstXAisXw8MGSLNMEtERFQVjJ6kbNy4EcHBwZg7dy5OnjyJ1q1bo3fv3khKSiq2/LZt2xAfH69dzp07B4VCgSFDhgAA7t+/j5MnT2L27Nk4efIktm3bhqioKLz44otFjjVv3jydY02ZMsWg12oqPCwsqqzc/v3SjQMBYOVKwNm5MpERERE9YvQ+KUuXLsWECRMwduxYAEBYWBh27NiBtWvXYubMmUXKOzo66jzfsGEDrK2ttUmKvb099uzZo1NmxYoV6NChA2JjY9GgQGdQW1tbuLu7V/UlmbwuDg7wVipLbPKRAfBWKtHFwaHU4yQmAq+8IjX3jB0LjB5d9bESEVHdVaGalIyMDKSlpRVZn5aWhszMTL2Pk5ubi8jISPTs2fNRQHI5evbsiYiICL2OER4ejmHDhsHGxqbUeGUyGRwKfeguWrQITk5OeOKJJ7BkyRLk5+eXeIycnBxkZmbqLDWVQibDghJ6tmq6yYb6+5faaValkvqhJCQALVpIs8oSERFVpQolKcOGDcOGDRuKrN+0aROGDRum93FSUlKgUqng5uams97NzQ0JCQll7n/8+HGcO3cOr732WollsrOzMWPGDAwfPlzndtBvvfUWNmzYgP379+P111/HwoUL8d5775V4nJCQENjb22sXHx8fPa7QdJ24excAYFYoEfFWKrGlRQsMdHEpdf8FC4B9+wBra2k+FGtrg4VKRER1VIWae44dO4alS5cWWd+tWzd88MEHlQ5KX+Hh4QgICECHDh2K3Z6Xl4ehQ4dCCIHVq1frbAsODtY+btWqFSwsLPD6668jJCQESqWyyLFmzZqls09mZmaNTVTO3LuHVXFxAICdAQEwl8l0Zpwta9ixEMCtW9LjsDCgeXNDR0xERHVRhZKUnJycYptG8vLy8ODBA72P4+zsDIVCgcTERJ31iYmJZfYVycrKwoYNGzBv3rxit2sSlBs3buCvv/7SqUUpTmBgIPLz83H9+nU0bdq0yHalUlls8lLTCCEw5coVqAEMdXFBr0J9fPQhkwFr1gBjxgCdOlV5iERERAAq2NzToUMHrFmzpsj6sLAwtG3bVu/jWFhYoG3btti3b592nVqtxr59+9CxY8dS9928eTNycnIwcuTIIts0CcqVK1ewd+9eODk5lRnL6dOnIZfL4erqqnf8NdFPSUk4mJEBa7kcnzVuXK59VSqpk6wGExQiIjKkCtWkfPLJJ+jZsyfOnDmDHj16AAD27duHEydO4M8//yzXsYKDgzF69Gi0a9cOHTp0QGhoKLKysrSjfUaNGgUvLy+EhITo7BceHo7+/fsXSUDy8vIwePBgnDx5Er///jtUKpW2f4ujoyMsLCwQERGBY8eOoXv37rC1tUVERASmT5+OkSNHon79+hX5ldQId/Pz8c7VqwCADxo2hI+lZbn2nz9fui/P//0fUEaXFSIiokqrUJLSuXNnREREYMmSJdi0aROsrKzQqlUrhIeHo0mTJuU61ssvv4zk5GTMmTMHCQkJaNOmDXbv3q3tTBsbGwu5XLfCJyoqCocOHSo2IYqLi8P27dsBAG3atNHZtn//fnTr1g1KpRIbNmzARx99hJycHPj5+WH69Ok6fU5qo/k3biA+Nxf+VlZ4u5z9afbuBebNk/qj/PUX8PLLBgqSiIjoIZkQorRbuFAJMjMzYW9vj4yMjDL7u5iCS1lZaPXvv8gTAjsCAvCCHk1gGvHxQJs2QFISMGGC1B+FiIioovT9DK1QTUpsbGyp2xtU4O65ZDhCCLwVHY08IdDPyalcCUp+vjRhW1IS0KoVsGyZAQMlIiIqoEJJiua+NyVRqVQVDoiq3i8pKdhz5w6UMhm+8Pcv177z5gEHDgD16gGbNgFWVoaJkYiIqLAKJSmnTp3SeZ6Xl4dTp05h6dKlWLBgQZUERlXjvkqF6dHRAIB3GzRA43JkGX/+CXzyifR4zRqgmJHZREREBlOhJKV169ZF1rVr1w6enp5YsmQJBg4cWOnAqGp8GhuLGzk5aKBUYlY5m+GcnQE/P6BXL2D4cAMFSEREVIIqvcFg06ZNceLEiao8JFXCtQcP8OnD/kNL/f1hrVCUa/8nnwROngT0vGkyERFRlapQklL45npCCMTHx+Ojjz4q9xBkMpzg6GjkCIEeDg4Y6Oys934JCYBmwl97ewMFR0REVIYKJSkODg5FOs4KIeDj41PsjQep+u1KTcWvqakwk8mwvEmTUjs6F/THH0D//sCSJcDkyYaNkYiIqDQVSlL279+v81wul8PFxQX+/v4wM6vSFiSqgBy1GlMfdpad6uWFx21sSiyrUgEHD0pzoZiZAW++CWRnAxcvVle0RERExavUZG4XLlxAbGwscnNzdda/+OKLlQ7M1JnyZG6fxsZi5rVrcLewQFSHDrArIXHctg2YOvXRHY01/PyACxeAcs6aT0REpBeDTuZ27do1DBw4EP/99x9kMhk0eY6mSYHzpBjPrexszL9+HQCwpFGjUhOUwYOlae4Lu34d2LkT4CAtIiIypgrdBXnq1Knw9fVFUlISrK2tce7cOfzzzz9o164dDhw4UMUhUnm8e+0astRqdLazw4iH9z8qTKWSalBKq0ObNk0qR0REZCwVSlIiIiIwb948ODs7Qy6XQ6FQ4Omnn0ZISAjeeuutqo6R9HTgzh1sSEqCHMCKUjrLHjxYtImnICGAmzelckRERMZSoSRFpVLB1tYWAODs7Izbt28DABo2bIioqKiqi470lq9WY8rDzrJveHqizcO/T3Hi4/U7pr7liIiIDKFCfVJatmyJM2fOwM/PD4GBgVi8eDEsLCywZs0aNGrUqKpjJD2sun0b57Ky4GRmhvl+fqWW9fDQ75j6liMiIjKECiUpH374IbKysgAA8+bNw//+9z906dIFTk5O2LhxY5UGSGVLzM3F7JgYAMDCRo3gaG5eavkuXQBvbyAurvh+KTKZtL1LF0NES0REpJ8KJSm9e/fWPvb398elS5eQlpaG+vXr6z1pGFWdWdeuIVOlQtt69TBej+oPhQJYtkwa3VOY5s8XGiqVIyIiMpYK9UkpjqOjIxMUIziakYFvExIASJ1lFXr+DQYOBMLDi6739ga2bOHwYyIiMj5OD1uDqYTA5CtXAABj3d3xVDlvtOPoKP1s0ABYtEjqg9KlC2tQiIjINDBJqcHWxscj8t492CsUCKlAh+XDh6WfvXsDw4dXcXBERESVVGXNPVS90vLyMOvaNQDAx35+cLOwKPcxNElK585VGRkREVHVYJJSQ82OiUFqfj5a2tggyNOz3PtnZwP//is9fvrpKg6OiIioCjBJqYFO372LsIcT6H3p7w8zefn/jP/+C+TmAm5uAKe2ISIiU8QkpYYRDzvLqgEMc3VFt/r1K3ScQ4ekn08//WjYMRERkSlhx9kaZl1iIg5nZsJaLseSSlSBDBgAWFoCTZpUYXBERERViElKDZKZn493H3aWnd2wIbwtLSt8rKZNpYWIiMhUsbmnBpl/4wYScnPRxMoK0318jB0OERGRQZlEkrJy5Ur4+vrC0tISgYGBOH78eIllu3XrBplMVmTp27evtowQAnPmzIGHhwesrKzQs2dPXHk46ZlGWloaRowYATs7Ozg4OGD8+PG4d++ewa6xsi5mZSH01i0AwHJ/fygr0FlW459/gO++A2Jjqyg4IiIiAzB6krJx40YEBwdj7ty5OHnyJFq3bo3evXsjKSmp2PLbtm1DfHy8djl37hwUCgWGDBmiLbN48WIsX74cYWFhOHbsGGxsbNC7d29kZ2dry4wYMQLnz5/Hnj178Pvvv+Off/7BxIkTDX69FSGEwFvR0cgXAi86OeF5J6dKHS88HBg7Fvj66yoKkIiIyBCEkXXo0EEEBQVpn6tUKuHp6SlCQkL02v+LL74Qtra24t69e0IIIdRqtXB3dxdLlizRlklPTxdKpVL89NNPQgghLly4IACIEydOaMvs2rVLyGQyERcXp9d5MzIyBACRkZGhV/nK2JKUJLB/v1AeOCCu3r9f6eM1biwEIMTu3VUQHBERUTnp+xlq1JqU3NxcREZGomfPntp1crkcPXv2REREhF7HCA8Px7Bhw2BjYwMAiImJQUJCgs4x7e3tERgYqD1mREQEHBwc0K5dO22Znj17Qi6X49ixY8WeJycnB5mZmTpLdbivUiE4OhoAMKNBAzSysqrU8RISgKtXpWHHTz1VFRESEREZhlGTlJSUFKhUKri5uemsd3NzQ8LDO/uW5vjx4zh37hxee+017TrNfqUdMyEhAa6urjrbzczM4OjoWOJ5Q0JCYG9vr118qqnj6qLYWMTm5KChUokZDRpU+niaqfADAoBy3o+QiIioWhm9T0plhIeHIyAgAB06dDD4uWbNmoWMjAztcvPmTYOf8+qDB1j8sHfrF/7+sK6C2xMXnMSNiIjIlBk1SXF2doZCoUBiYqLO+sTERLi7u5e6b1ZWFjZs2IDx48frrNfsV9ox3d3di3TMzc/PR1paWonnVSqVsLOz01kMbXp0NHKEQK/69dHf2blKjsmbChIRUU1h1CTFwsICbdu2xb59+7Tr1Go19u3bh44dO5a67+bNm5GTk4ORI0fqrPfz84O7u7vOMTMzM3Hs2DHtMTt27Ij09HRERkZqy/z1119Qq9UIDAysikurtJ2pqfgtNRVmMhmW+/tDVgVz1z94AJw6JT1mTQoREZk6o884GxwcjNGjR6Ndu3bo0KEDQkNDkZWVhbFjxwIARo0aBS8vL4SEhOjsFx4ejv79+8Op0HBcmUyGadOm4ZNPPkGTJk3g5+eH2bNnw9PTE/379wcANG/eHM8//zwmTJiAsLAw5OXlYfLkyRg2bBg8K3BH4aqWo1Zj6sPOstO9vdHsYafgyrKyAm7fBk6cAKqgewsREZFBGT1Jefnll5GcnIw5c+YgISEBbdq0we7du7UdX2NjYyEvNHFZVFQUDh06hD///LPYY7733nvIysrCxIkTkZ6ejqeffhq7d++GZYFp5NetW4fJkyejR48ekMvlGDRoEJYvX264Cy2DSggcTE9HfG4u/rpzB9EPHsDDwgKzGzas0vO4uAAvvFClhyQiIjIImRBCGDuImigzMxP29vbIyMiodP+UbcnJmBodjVs5OTrrp3p5IZR3ACQiolpG38/QGj26pzbYlpyMwefPF0lQAGB5XBy2JSdXyXlUKqBfP2DuXMCEZ/8nIiLSYpJiRCohMDU6GqVVZU2LjoaqCiq7zp0Dfv8d+OILoBI3TyYiIqo2TFKM6GB6erE1KBoCwM2cHBxMT6/0uTTzozz1FGBm9J5IREREZWOSYkTxublVWq40mvlROPSYiIhqCiYpRuRhYVGl5UrDSdyIiKimYZJiRF0cHOCtVKKkadpkAHyUSnRxcKjUeW7eBGJjAYUCMJG56oiIiMrEJMWIFDIZlvn7A0CRREXzPNTfH4pKzjarqUVp0waoV69ShyIiIqo2TFKMbKCLC7a0aAEvpVJnvbdSiS0tWmCgi0ulz5GWBtjZsT8KERHVLJzMrYKqcjI3QHfGWQ8LC3RxcKh0DYrO8VXA/fuArW2VHZKIiKhC9P0M5WBUE6GQydCtfn3DHV/BBIWIiGoWNvfUciqVsSMgIiKqGCYptdy8eUCjRsBXXxk7EiIiovJhklLLHToExMQYOwoiIqLyY5JSi+XlAceOSY85soeIiGoaJim12JkzQFYW4OAANG9u7GiIiIjKh0lKLaaZxK1TJ0DOvzQREdUw/OiqxTR3PmZTDxER1URMUmopIXhTQSIiqtmYpNRSOTlA//7AE08A7dsbOxoiIqLy44yztZSlJbBqlbGjICIiqjjWpBAREZFJYpJSS508KTX5EBER1VRMUmqhtDSgbVvA3h7IyDB2NERERBXDJKUWioiQfjZsKCUqRERENRGTlFpIMz8Khx4TEVFNxiSlFtLMj8JJ3IiIqCYzepKycuVK+Pr6wtLSEoGBgTh+/Hip5dPT0xEUFAQPDw8olUo89thj2Llzp3a7r68vZDJZkSUoKEhbplu3bkW2v/HGGwa7xuqUkwOcOCE9ZpJCREQ1mVHnSdm4cSOCg4MRFhaGwMBAhIaGonfv3oiKioKrq2uR8rm5uejVqxdcXV2xZcsWeHl54caNG3BwcNCWOXHiBFQqlfb5uXPn0KtXLwwZMkTnWBMmTMC8efO0z62trav+Ao3g5EkgOxtwcQGaNDF2NERERBVn1CRl6dKlmDBhAsaOHQsACAsLw44dO7B27VrMnDmzSPm1a9ciLS0NR44cgbm5OQCp5qQgFxcXneeLFi1C48aN0bVrV5311tbWcHd31zvWnJwc5BQY05uZman3vtWp4FT4MplxYyEiIqoMozX35ObmIjIyEj179nwUjFyOnj17IkIzPKWQ7du3o2PHjggKCoKbmxtatmyJhQsX6tScFD7Hjz/+iHHjxkFW6BN73bp1cHZ2RsuWLTFr1izcv3+/1HhDQkJgb2+vXXx8fMp5xdXjhReAxYuB0aONHQkREVHlGK0mJSUlBSqVCm5ubjrr3dzccOnSpWL3uXbtGv766y+MGDECO3fuRHR0NCZNmoS8vDzMnTu3SPlffvkF6enpGDNmjM76V155BQ0bNoSnpyf+++8/zJgxA1FRUdi2bVuJ8c6aNQvBwcHa55mZmSaZqDz+uLQQERHVdDXq3j1qtRqurq5Ys2YNFAoF2rZti7i4OCxZsqTYJCU8PBx9+vSBp6enzvqJEydqHwcEBMDDwwM9evTA1atX0bhx42LPrVQqoVQqq/aCiIiIqERGS1KcnZ2hUCiQmJiosz4xMbHEviIeHh4wNzeHQqHQrmvevDkSEhKQm5sLCwsL7fobN25g7969pdaOaAQGBgIAoqOjS0xSaoIjR4CYGKBbN8DLy9jREBERVY7R+qRYWFigbdu22Ldvn3adWq3Gvn370LFjx2L36dy5M6Kjo6FWq7XrLl++DA8PD50EBQC+/fZbuLq6om/fvmXGcvr0aQBSElSTffMNMHIksHKlsSMhIiKqPKPOkxIcHIyvv/4a33//PS5evIg333wTWVlZ2tE+o0aNwqxZs7Tl33zzTaSlpWHq1Km4fPkyduzYgYULF+rMgQJIyc63336L0aNHw8xMt7Lo6tWrmD9/PiIjI3H9+nVs374do0aNwjPPPINWrVoZ/qINqODIHiIioprOqH1SXn75ZSQnJ2POnDlISEhAmzZtsHv3bm1n2tjYWMjlj/IoHx8f/PHHH5g+fTpatWoFLy8vTJ06FTNmzNA57t69exEbG4tx48YVOaeFhQX27t2L0NBQZGVlwcfHB4MGDcKHH35o2Is1sKQk4PJl6XGnTsaNhYiIqCrIhBDC2EHURJmZmbC3t0dGRgbs7OyMHQ5++QUYMABo0QI4d87Y0RAREZVM389Qo0+LT1WDNxUkIqLahklKLcGbChIRUW3DJKUWyM4GIiOlx6xJISKi2qJGTeZGxbO0BG7cAI4dA/z8jB0NERFR1WCSUkt4eAD9+xs7CiIioqrD5h4iIiIySUxSaji1Ghg0CPjkE+DePWNHQ0REVHWYpNRwFy4A27YBixZJfVOIiIhqCyYpNZxm6HFgIGDGHkZERFSLMEmp4TSTuHF+FCIiqm2YpNRwvKkgERHVVkxSarDbt4GYGEAuB556ytjREBERVS0mKTWYphalVSvABO5xSEREVKWYpNRgSUmAjQ37oxARUe0kE0IIYwdRE+l7m2lDy88HsrIAe3ujhUBERFQu+n6GsialhjMzY4JCRES1E5OUGkqtNnYEREREhsUkpYb65BOgWTPg66+NHQkREZFhMEmpoQ4dAqKigLw8Y0dCRERkGExSaqD8fCAiQnrMSdyIiKi2YpJSA509K93x2M4OaNnS2NEQEREZBpOUGkgziVvHjoBCYdxYiIiIDIVJSg2kuakgm3qIiKg2Y5JSwwjBOx8TEVHdwCSlhsnJAfr0AVq0ADp0MHY0REREhmP0JGXlypXw9fWFpaUlAgMDcfz48VLLp6enIygoCB4eHlAqlXjsscewc+dO7faPPvoIMplMZ2nWrJnOMbKzsxEUFAQnJyfUq1cPgwYNQmJiokGur6pZWkpzo5w7J923h4iIqLYyapKyceNGBAcHY+7cuTh58iRat26N3r17Iykpqdjyubm56NWrF65fv44tW7YgKioKX3/9Nby8vHTKtWjRAvHx8drlkKZ95KHp06fjt99+w+bNm/H333/j9u3bGDhwoMGuk4iIiMrPzJgnX7p0KSZMmICxY8cCAMLCwrBjxw6sXbsWM2fOLFJ+7dq1SEtLw5EjR2Bubg4A8PX1LVLOzMwM7u7uxZ4zIyMD4eHhWL9+PZ599lkAwLfffovmzZvj6NGjeOqpp6ro6gzj7FlpptmHl09ERFRrGa0mJTc3F5GRkejZs+ejYORy9OzZExGamcoK2b59Ozp27IigoCC4ubmhZcuWWLhwIVQqlU65K1euwNPTE40aNcKIESMQGxur3RYZGYm8vDyd8zZr1gwNGjQo8bwAkJOTg8zMTJ2luqWnA61bAw4OwJ071X56IiKiamW0JCUlJQUqlQpubm46693c3JCQkFDsPteuXcOWLVugUqmwc+dOzJ49G59//jk++eQTbZnAwEB899132L17N1avXo2YmBh06dIFd+/eBQAkJCTAwsICDg4Oep8XAEJCQmBvb69dfHx8KnjlFXf0qDS6x8MDqF+/2k9PRERUrYza3FNearUarq6uWLNmDRQKBdq2bYu4uDgsWbIEc+fOBQD06dNHW75Vq1YIDAxEw4YNsWnTJowfP77C5541axaCg4O1zzMzM6s9UdFM4sb5UYiIqC4wWpLi7OwMhUJRZFRNYmJiif1JPDw8YG5uDkWBaVabN2+OhIQE5ObmwsLCosg+Dg4OeOyxxxAdHQ0AcHd3R25uLtLT03VqU0o7LwAolUoolcryXGKV4/woRERUlxitucfCwgJt27bFvn37tOvUajX27duHjh07FrtP586dER0dDbVarV13+fJleHh4FJugAMC9e/dw9epVeHh4AADatm0Lc3NznfNGRUUhNja2xPOagrw84Ngx6TFrUoiIqC4w6hDk4OBgfP311/j+++9x8eJFvPnmm8jKytKO9hk1ahRmzZqlLf/mm28iLS0NU6dOxeXLl7Fjxw4sXLgQQUFB2jLvvPMO/v77b1y/fh1HjhzBgAEDoFAoMHz4cACAvb09xo8fj+DgYOzfvx+RkZEYO3YsOnbsaNIje06dAh48ABwdpdE9REREtZ1R+6S8/PLLSE5Oxpw5c5CQkIA2bdpg9+7d2s60sbGxkMsf5VE+Pj74448/MH36dLRq1QpeXl6YOnUqZsyYoS1z69YtDB8+HKmpqXBxccHTTz+No0ePwsXFRVvmiy++gFwux6BBg5CTk4PevXtj1apV1XfhFaDpj9KpEyA3+hR8REREhicTQghjB1ETZWZmwt7eHhkZGbCzszP4+c6dA7ZvBx57DBg82OCnIyIiMhh9P0Nr1OieuqxlS2khIiKqK9hwQERERCaJSUoNcPw4sGkTUMpcc0RERLUOk5Qa4JtvgJdfBr74wtiREBERVR8mKTWAZhI3zo9CRER1CZMUE5eaCly8KD3u1Mm4sRAREVUnJikm7sgR6WezZoCzs3FjISIiqk5MUkwcbypIRER1FZMUE8ebChIRUV3FJMWE5eQA//4rPWZNChER1TWccdaEKZXA5cvA0aOAv7+xoyEiIqpeTFJMXIMG0kJERFTXsLmHiIiITBKTFBOlVgPDhwOffgrcu2fsaIiIiKofkxQTFRUFbNgAfPQRYGFh7GiIiIiqH5MUE6WZHyUwkEkKERHVTUxSTBQncSMiorqOSYqJ4iRuRERU1zFJMUGJiUB0NCCTAR07GjsaIiIi42CSYoI0TT0tWwIODkYNhYiIyGiYpJig27cBS0v2RyEiorpNJoQQxg6iJsrMzIS9vT0yMjJgZ2dX5cfPzZXmR3F0rPJDExERGZW+n6GsSTFRFhZMUIiIqG5jkmJiWK9FREQkYZJiYhYtkjrMfvONsSMhIiIyLiYpJubgQeD8eeDBA2NHQkREZFxGT1JWrlwJX19fWFpaIjAwEMePHy+1fHp6OoKCguDh4QGlUonHHnsMO3fu1G4PCQlB+/btYWtrC1dXV/Tv3x9RUVE6x+jWrRtkMpnO8sYbbxjk+spDrQaOHJEec2QPERHVdUZNUjZu3Ijg4GDMnTsXJ0+eROvWrdG7d28kJSUVWz43Nxe9evXC9evXsWXLFkRFReHrr7+Gl5eXtszff/+NoKAgHD16FHv27EFeXh6ee+45ZGVl6RxrwoQJiI+P1y6LFy826LWWRaUCvvsOyMiQhh+3aGHUcIiIiIzOqEOQAwMD0b59e6xYsQIAoFar4ePjgylTpmDmzJlFyoeFhWHJkiW4dOkSzM3N9TpHcnIyXF1d8ffff+OZZ54BINWktGnTBqGhoRWOvSqHIG/bBkydCty69WidtzewbBkwcGClDk1ERGRyTH4Icm5uLiIjI9GzZ89Hwcjl6NmzJyIiIordZ/v27ejYsSOCgoLg5uaGli1bYuHChVCpVCWeJyMjAwDgWGg877p16+Ds7IyWLVti1qxZuH//fqnx5uTkIDMzU2epCtu2AYMH6yYoABAXJ63ftq1KTkNERFTjmBnrxCkpKVCpVHBzc9NZ7+bmhkuXLhW7z7Vr1/DXX39hxIgR2LlzJ6KjozFp0iTk5eVh7ty5Rcqr1WpMmzYNnTt3RsuWLbXrX3nlFTRs2BCenp7477//MGPGDERFRWFbKRlBSEgIPv744wpebfFUKqkGpbi6LCGke/dMmwa89BKgUFTpqYmIiEye0ZKUilCr1XB1dcWaNWugUCjQtm1bxMXFYcmSJcUmKUFBQTh37hwOaW4p/NDEiRO1jwMCAuDh4YEePXrg6tWraNy4cbHnnjVrFoKDg7XPMzMz4ePjU6nrOXiwaA1KQUIAN29K5bp1q9SpiIiIahyjJSnOzs5QKBRITEzUWZ+YmAh3d/di9/Hw8IC5uTkUBaoVmjdvjoSEBOTm5sLCwkK7fvLkyfj999/xzz//wNvbu9RYAgMDAQDR0dElJilKpRJKpVKva9NXfHzVliMiIqpNjNYnxcLCAm3btsW+ffu069RqNfbt24eOHTsWu0/nzp0RHR0NtVqtXXf58mV4eHhoExQhBCZPnoyff/4Zf/31F/z8/MqM5fTp0wCkJKg66Xu6ag6LiIjIJBh1CHJwcDC+/vprfP/997h48SLefPNNZGVlYezYsQCAUaNGYdasWdryb775JtLS0jB16lRcvnwZO3bswMKFCxEUFKQtExQUhB9//BHr16+Hra0tEhISkJCQgAcPZ0e7evUq5s+fj8jISFy/fh3bt2/HqFGj8Mwzz6BVq1bVev1dukijeGSy4rfLZICPj1SOiIiozhFG9uWXX4oGDRoICwsL0aFDB3H06FHttq5du4rRo0frlD9y5IgIDAwUSqVSNGrUSCxYsEDk5+drtwModvn222+FEELExsaKZ555Rjg6OgqlUin8/f3Fu+++KzIyMsoVd0ZGhgBQ7v0K27pVCJlMWqReKNKiWbd1a6UOT0REZHL0/Qw16jwpNZmh50nx8QFCQzlPChER1T76fobWqNE9tdXAgdIw44MHpU6yHh5SEw+HHRMRUV3GJMVEKBQcZkxERFSQ0W8wSERERFQcJilERERkkpikEBERkUlikkJEREQmiUkKERERmSQmKURERGSSOAS5gjRz4GVmZho5EiIioppF89lZ1nyyTFIq6O7duwAAHx8fI0dCRERUM929exf29vYlbue0+BWkVqtx+/Zt2NraQlbSHQJriMzMTPj4+ODmzZuVnuK/Jqhr1wvUvWuua9cL1L1r5vXWbEII3L17F56enpDLS+55wpqUCpLL5fD29jZ2GFXKzs6uVrz49VXXrheoe9dc164XqHvXzOutuUqrQdFgx1kiIiIySUxSiIiIyCQxSSEolUrMnTsXSqXS2KFUi7p2vUDdu+a6dr1A3btmXm/dwI6zREREZJJYk0JEREQmiUkKERERmSQmKURERGSSmKQQERGRSWKSUoeFhISgffv2sLW1haurK/r374+oqChjh1VtFi1aBJlMhmnTphk7FIOJi4vDyJEj4eTkBCsrKwQEBODff/81dlgGo1KpMHv2bPj5+cHKygqNGzfG/Pnz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",
      "text/plain": [
       "<Figure size 600x400 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "\n",
       "<style>\n",
       "    /* background: */\n",
       "    progress::-webkit-progress-bar {background-color: #CDCDCD; width: 100%;}\n",
       "    progress {background-color: #CDCDCD;}\n",
       "\n",
       "    /* value: */\n",
       "    progress::-webkit-progress-value {background-color: #00BFFF  !important;}\n",
       "    progress::-moz-progress-bar {background-color: #00BFFF  !important;}\n",
       "    progress {color: #00BFFF ;}\n",
       "\n",
       "    /* optional */\n",
       "    .progress-bar-interrupted, .progress-bar-interrupted::-webkit-progress-bar {\n",
       "        background: #000000;\n",
       "    }\n",
       "</style>\n"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "\n",
       "    <div>\n",
       "      <progress value='15' class='progress-bar-interrupted' max='30' style='width:300px; height:20px; vertical-align: middle;'></progress>\n",
       "      50.00% [15/30] [18:14<18:14]\n",
       "      <br>\n",
       "      ████████████████████100.00% [79/79] [val_loss=0.4828, val_auc=0.7708]\n",
       "    </div>\n",
       "    "
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[0;31m<<<<<< val_auc without improvement in 5 epoch,early stopping >>>>>> \n",
      "\u001b[0m\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>epoch</th>\n",
       "      <th>train_loss</th>\n",
       "      <th>train_auc</th>\n",
       "      <th>lr</th>\n",
       "      <th>val_loss</th>\n",
       "      <th>val_auc</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>1</td>\n",
       "      <td>1.408187</td>\n",
       "      <td>0.623909</td>\n",
       "      <td>0.001</td>\n",
       "      <td>0.619509</td>\n",
       "      <td>0.692739</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>2</td>\n",
       "      <td>0.584702</td>\n",
       "      <td>0.712103</td>\n",
       "      <td>0.001</td>\n",
       "      <td>0.541251</td>\n",
       "      <td>0.727292</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>3</td>\n",
       "      <td>0.528367</td>\n",
       "      <td>0.739340</td>\n",
       "      <td>0.001</td>\n",
       "      <td>0.508937</td>\n",
       "      <td>0.747557</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>4</td>\n",
       "      <td>0.502788</td>\n",
       "      <td>0.755457</td>\n",
       "      <td>0.001</td>\n",
       "      <td>0.495135</td>\n",
       "      <td>0.756461</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>5</td>\n",
       "      <td>0.487692</td>\n",
       "      <td>0.767115</td>\n",
       "      <td>0.001</td>\n",
       "      <td>0.488027</td>\n",
       "      <td>0.761710</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>6</td>\n",
       "      <td>0.478542</td>\n",
       "      <td>0.774563</td>\n",
       "      <td>0.001</td>\n",
       "      <td>0.484223</td>\n",
       "      <td>0.768054</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>7</td>\n",
       "      <td>0.471563</td>\n",
       "      <td>0.780906</td>\n",
       "      <td>0.001</td>\n",
       "      <td>0.481900</td>\n",
       "      <td>0.766908</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>8</td>\n",
       "      <td>0.465800</td>\n",
       "      <td>0.786392</td>\n",
       "      <td>0.001</td>\n",
       "      <td>0.482296</td>\n",
       "      <td>0.768268</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8</th>\n",
       "      <td>9</td>\n",
       "      <td>0.460357</td>\n",
       "      <td>0.791760</td>\n",
       "      <td>0.001</td>\n",
       "      <td>0.481996</td>\n",
       "      <td>0.770032</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9</th>\n",
       "      <td>10</td>\n",
       "      <td>0.456565</td>\n",
       "      <td>0.795760</td>\n",
       "      <td>0.001</td>\n",
       "      <td>0.480180</td>\n",
       "      <td>0.771727</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>10</th>\n",
       "      <td>11</td>\n",
       "      <td>0.452726</td>\n",
       "      <td>0.799583</td>\n",
       "      <td>0.001</td>\n",
       "      <td>0.478768</td>\n",
       "      <td>0.769339</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>11</th>\n",
       "      <td>12</td>\n",
       "      <td>0.449234</td>\n",
       "      <td>0.803089</td>\n",
       "      <td>0.001</td>\n",
       "      <td>0.480701</td>\n",
       "      <td>0.769966</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>12</th>\n",
       "      <td>13</td>\n",
       "      <td>0.445749</td>\n",
       "      <td>0.806548</td>\n",
       "      <td>0.001</td>\n",
       "      <td>0.480627</td>\n",
       "      <td>0.769630</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>13</th>\n",
       "      <td>14</td>\n",
       "      <td>0.441975</td>\n",
       "      <td>0.810381</td>\n",
       "      <td>0.001</td>\n",
       "      <td>0.480726</td>\n",
       "      <td>0.769027</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>14</th>\n",
       "      <td>15</td>\n",
       "      <td>0.438493</td>\n",
       "      <td>0.813862</td>\n",
       "      <td>0.001</td>\n",
       "      <td>0.482766</td>\n",
       "      <td>0.770836</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "    epoch  train_loss  train_auc     lr  val_loss   val_auc\n",
       "0       1    1.408187   0.623909  0.001  0.619509  0.692739\n",
       "1       2    0.584702   0.712103  0.001  0.541251  0.727292\n",
       "2       3    0.528367   0.739340  0.001  0.508937  0.747557\n",
       "3       4    0.502788   0.755457  0.001  0.495135  0.756461\n",
       "4       5    0.487692   0.767115  0.001  0.488027  0.761710\n",
       "5       6    0.478542   0.774563  0.001  0.484223  0.768054\n",
       "6       7    0.471563   0.780906  0.001  0.481900  0.766908\n",
       "7       8    0.465800   0.786392  0.001  0.482296  0.768268\n",
       "8       9    0.460357   0.791760  0.001  0.481996  0.770032\n",
       "9      10    0.456565   0.795760  0.001  0.480180  0.771727\n",
       "10     11    0.452726   0.799583  0.001  0.478768  0.769339\n",
       "11     12    0.449234   0.803089  0.001  0.480701  0.769966\n",
       "12     13    0.445749   0.806548  0.001  0.480627  0.769630\n",
       "13     14    0.441975   0.810381  0.001  0.480726  0.769027\n",
       "14     15    0.438493   0.813862  0.001  0.482766  0.770836"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "keras_model.fit(\n",
    "    train_data = dl_train,\n",
    "    val_data= dl_val,\n",
    "    ckpt_path='checkpoint',\n",
    "    epochs=30,\n",
    "    patience=5,\n",
    "    monitor=\"val_auc\", \n",
    "    mode=\"max\",\n",
    "    plot = True,\n",
    "    wandb = False\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "26147264-b2c2-4da6-986a-b721d1373c5d",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "bec9f969",
   "metadata": {},
   "source": [
    "## 四，评估模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "60a0d622",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "100%|██████████████████████████████| 313/313 [00:54<00:00,  5.78it/s, val_auc=0.818, val_loss=0.434]\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'val_loss': 0.4341495612177986, 'val_auc': 0.817977249622345}"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "keras_model.evaluate(dl_train)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "d6d68615",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "100%|█████████████████████████████████| 79/79 [00:13<00:00,  5.95it/s, val_auc=0.772, val_loss=0.48]\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'val_loss': 0.48018012695674656, 'val_auc': 0.771727442741394}"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "keras_model.evaluate(dl_val)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "6de6ee16",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "100%|█████████████████████████████████| 98/98 [00:17<00:00,  5.69it/s, val_auc=0.77, val_loss=0.481]\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'val_loss': 0.48122845286009264, 'val_auc': 0.7695667743682861}"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "keras_model.evaluate(dl_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e785f2fa",
   "metadata": {},
   "source": [
    "## 五，使用模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "4e55e8fc",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 98/98 [00:02<00:00, 47.45it/s]\n"
     ]
    }
   ],
   "source": [
    "from tqdm import tqdm \n",
    "\n",
    "net,dl_test = keras_model.accelerator.prepare(net,dl_test)\n",
    "net.eval()\n",
    "preds = []\n",
    "with torch.no_grad():\n",
    "    for batch in tqdm(dl_test):\n",
    "        preds.append(net.predict(batch))\n",
    "    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "5c30b8f0",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "yhat_list = [yd.sigmoid().reshape(-1).tolist() for yd in preds]\n",
    "yhat = []\n",
    "for yd in yhat_list:\n",
    "    yhat.extend(yd)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "168cb211",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "dftest_raw = dftest_raw.rename(columns = {target_col: 'y'})\n",
    "dftest_raw['yhat'] = yhat"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "6d5e7ab7",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.7695669193178376"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from sklearn.metrics import roc_auc_score\n",
    "roc_auc_score(dftest_raw['y'],dftest_raw['yhat'])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8e8f8552",
   "metadata": {},
   "source": [
    "## 六，保存模型"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9eeae65b",
   "metadata": {},
   "source": [
    "最佳模型权重已经保存在ckpt_path = 'checkpoint'位置了。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "7eb2899d",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<All keys matched successfully>"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "net.load_state_dict(torch.load('checkpoint',weights_only=True))"
   ]
  }
 ],
 "metadata": {
  "kaggle": {
   "accelerator": "gpu",
   "dataSources": [
    {
     "datasetId": 5446668,
     "sourceId": 9035782,
     "sourceType": "datasetVersion"
    }
   ],
   "dockerImageVersionId": 30747,
   "isGpuEnabled": true,
   "isInternetEnabled": true,
   "language": "python",
   "sourceType": "notebook"
  },
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
